﻿using System;
using System.Windows;
using System.Windows.Media;

namespace LooseChart
{
    /// <summary>
    /// 时间轴--单位：秒
    /// </summary>
    public class DateTimeAxis : IAxis
    {
        public DateTimeAxis() : base() { }

        public override double ValueToAxisValue(double value, ValueAxisConvertParamBase param)
        {
            return value;
        }

        public override double AxisValueToValue(double axisvalue, ValueAxisConvertParamBase param)
        {
            return axisvalue;
        }
        public static readonly long ThicksToSeconds = 10000000;
        private static readonly double DateTimeAxisMaxValue = DateTime.MaxValue.Ticks / ThicksToSeconds;
        internal override void SetRange(double max, double min)
        {
            Range = new AxisRange() { Max = !double.IsNaN(max) && !double.IsInfinity(max) && Math.Ceiling(max) < DateTimeAxisMaxValue ? max : Range.Max, Min = !double.IsNaN(min) && !double.IsInfinity(min) && Math.Floor(min) > 0 ? min : Range.Min };
        }

        public override void Draw()
        {
            if (!this.IsLoaded) return;
            var convertParam = GetConvertParam();
            var begin = new DateTime(Convert.ToInt64(convertParam.MinAxisValue * ThicksToSeconds));
            var end = new DateTime(Convert.ToInt64(convertParam.MaxAxisValue * ThicksToSeconds));
            using (var dc = DV.RenderOpen())
            {
                dc.DrawRectangle(Background, null, new Rect(0, 0, this.ActualWidth, this.ActualHeight));
                if (convertParam.AxisType == AxisType.X) dc.DrawLine(LinePen, new Point(0, 0), new Point(this.ActualWidth, 0));
                else if (convertParam.AxisType == AxisType.Y) dc.DrawLine(LinePen, new Point(this.ActualWidth, 0), new Point(this.ActualWidth, this.ActualHeight));
                else dc.DrawLine(LinePen, new Point(0, 0), new Point(0, this.ActualHeight));

                var sub = end - begin;
                if (sub.TotalDays > 365) DrawYear(dc, begin, end, convertParam);
                else if (sub.TotalDays > 30) DrawMonth(dc, begin, end, convertParam);
                else if (sub.TotalDays > 1) DrawDay(dc, begin, end, convertParam);
                else if (sub.TotalHours > 1) DrawHour(dc, begin, end, convertParam);
                else if (sub.TotalMinutes > 1) DrawMinute(dc, begin, end, convertParam);
                else if (sub.TotalSeconds > 1) DrawSecond(dc, begin, end, convertParam);
                else CreateTips(dc, begin.ToString("yyyy-MM-dd HH:mm:ss.fff"), end.ToString("yyyy-MM-dd HH:mm:ss.fff"), convertParam);
            }
        }

        /// <summary>
        /// 跨年绘制
        /// </summary>
        /// <param name="dc"></param>
        /// <param name="begin"></param>
        /// <param name="end"></param>
        /// <param name="convertParam"></param>
        private void DrawYear(DrawingContext dc, DateTime begin, DateTime end, ValueLocationConvertParam convertParam)
        {
            CreateTips(dc, begin.ToString("yyyy"), end.ToString("yyyy"), convertParam);
            int maxCount = Convert.ToInt32(convertParam.Length / (4 * FontSize));
            if (maxCount <= 0) return;
            var start = begin.Year;
            var stop = end.Year;
            var step = Convert.ToInt32(Math.Ceiling((stop - start) * 1d / maxCount));
            var stepLength = GetValueLocation(new DateTime(start + step, 1, 1).Ticks / ThicksToSeconds, convertParam) - GetValueLocation(new DateTime(start, 1, 1).Ticks / ThicksToSeconds, convertParam);
            var smallMaxCount = Convert.ToInt32(Math.Ceiling(Math.Abs(stepLength) / 20));
            var smallStep = Convert.ToInt32(Math.Ceiling(step * 12d / smallMaxCount));
            if (smallStep >= 12) smallStep = Convert.ToInt32(Math.Ceiling(smallStep / 12d) * 12);
            else if (smallStep >= 6) smallStep = 6;
            else if (smallStep >= 3) smallStep = 3;
            else if (smallStep >= 2) smallStep = 2;
            else if (smallStep >= 1) smallStep = 1;
            else smallStep = 0;
            for (int y = start; y <= stop; y += step)
            {
                var date = new DateTime(y, 1, 1);
                var location = GetValueLocation(date.Ticks / ThicksToSeconds, convertParam);
                DrawSeparate(dc, location, y.ToString(), FontSize, 5, convertParam);
                if (smallStep <= 0) continue;
                for (int m = 1; m <= step * 12 / smallStep; m++) DrawSmallSeparate(dc, GetValueLocation(date.AddMonths(m * smallStep).Ticks / ThicksToSeconds, convertParam), convertParam);
            }
        }
        /// <summary>
        /// 跨月绘制
        /// </summary>
        /// <param name="dc"></param>
        /// <param name="begin"></param>
        /// <param name="end"></param>
        /// <param name="convertParam"></param>
        private void DrawMonth(DrawingContext dc, DateTime begin, DateTime end, ValueLocationConvertParam convertParam)
        {
            CreateTips(dc, begin.ToString("yyyy"), end.ToString("yyyy"), convertParam);
            int maxCount = Convert.ToInt32(convertParam.Length / (2 * FontSize));
            if (maxCount <= 0) return;
            var count = (end.Year - begin.Year) * 12 + end.Month - begin.Month + 1;
            var step = Convert.ToInt32(Math.Ceiling(count * 1d / maxCount));
            var startDate = begin.Date.AddDays(1 - begin.Date.Day);
            for (int y = 0; y <= count; y += step)
            {
                var date = startDate.AddMonths(y);
                var location = GetValueLocation(date.Ticks / ThicksToSeconds, convertParam);
                DrawSeparate(dc, location, date.Month.ToString(), FontSize, 5, convertParam);
            }
        }
        /// <summary>
        /// 跨天绘制
        /// </summary>
        /// <param name="dc"></param>
        /// <param name="begin"></param>
        /// <param name="end"></param>
        /// <param name="convertParam"></param>
        private void DrawDay(DrawingContext dc, DateTime begin, DateTime end, ValueLocationConvertParam convertParam)
        {
            CreateTips(dc, begin.ToString("yyyy-MM"), end.ToString("yyyy-MM"), convertParam);
            int maxCount = Convert.ToInt32(convertParam.Length / (2 * FontSize));
            if (maxCount <= 0) return;
            var count = (end - begin).TotalDays + 1;
            var step = Convert.ToInt32(Math.Ceiling(count / maxCount));
            var stepLength = convertParam.Length * step / count;
            var smallMaxCount = Convert.ToInt32(Math.Ceiling(stepLength / 20));
            var smallStep = Convert.ToInt32(Math.Ceiling(step * 24d / smallMaxCount));
            if (smallStep >= 24) smallStep = Convert.ToInt32(Math.Ceiling(smallStep / 24d) * 24);
            else if (smallStep >= 12) smallStep = 12;
            else if (smallStep >= 6) smallStep = 6;
            else if (smallStep >= 3) smallStep = 3;
            else if (smallStep >= 2) smallStep = 2;
            else if (smallStep >= 1) smallStep = 1;
            else smallStep = 0;
            var startDate = begin.Date;
            for (int y = 0; y <= count; y += step)
            {
                var date = startDate.AddDays(y);
                var location = GetValueLocation(date.Ticks / ThicksToSeconds, convertParam);
                DrawSeparate(dc, location, date.Day.ToString(), FontSize, 5, convertParam);
                if (smallStep <= 0) continue;
                for (int m = 1; m <= step * 24 / smallStep; m++) DrawSmallSeparate(dc, GetValueLocation(date.AddHours(m * smallStep).Ticks / ThicksToSeconds, convertParam), convertParam);
            }
        }
        /// <summary>
        /// 跨小时绘制
        /// </summary>
        /// <param name="dc"></param>
        /// <param name="begin"></param>
        /// <param name="end"></param>
        /// <param name="convertParam"></param>
        private void DrawHour(DrawingContext dc, DateTime begin, DateTime end, ValueLocationConvertParam convertParam)
        {
            CreateTips(dc, begin.ToString("yyyy-MM-dd"), end.ToString("yyyy-MM-dd"), convertParam);
            int maxCount = Convert.ToInt32(convertParam.Length / (2 * FontSize));
            if (maxCount <= 0) return;
            var count = (end - begin).TotalHours + 1;
            var step = Convert.ToInt32(Math.Ceiling(count / maxCount));
            var stepLength = convertParam.Length * step / count;
            var smallMaxCount = Convert.ToInt32(Math.Ceiling(stepLength / 20));
            var smallStep = Convert.ToInt32(Math.Ceiling(step * 60d / smallMaxCount));
            if (smallStep >= 60) smallStep = Convert.ToInt32(Math.Ceiling(smallStep / 60d) * 60);
            else if (smallStep >= 30) smallStep = 30;
            else if (smallStep >= 20) smallStep = 20;
            else if (smallStep >= 10) smallStep = 10;
            else if (smallStep >= 5) smallStep = 5;
            else smallStep = 0;
            var startDate = new DateTime(begin.Year, begin.Month, begin.Day, begin.Hour, 0, 0);
            for (int y = 0; y <= count; y += step)
            {
                var date = startDate.AddHours(y);
                var location = GetValueLocation(date.Ticks / ThicksToSeconds, convertParam);
                DrawSeparate(dc, location, date.Hour.ToString(), FontSize, 5, convertParam);
                if (smallStep <= 0) continue;
                for (int m = 1; m <= step * 60 / smallStep; m++) DrawSmallSeparate(dc, GetValueLocation(date.AddMinutes(m * smallStep).Ticks / ThicksToSeconds, convertParam), convertParam);
            }
        }
        /// <summary>
        /// 跨分钟绘制
        /// </summary>
        /// <param name="dc"></param>
        /// <param name="begin"></param>
        /// <param name="end"></param>
        /// <param name="convertParam"></param>
        private void DrawMinute(DrawingContext dc, DateTime begin, DateTime end, ValueLocationConvertParam convertParam)
        {
            CreateTips(dc, begin.ToString("yyyy-MM-dd HH"), end.ToString("yyyy-MM-dd HH"), convertParam);
            int maxCount = Convert.ToInt32(convertParam.Length / (2 * FontSize));
            if (maxCount <= 0) return;
            var count = (end - begin).TotalMinutes + 1;
            var step = Convert.ToInt32(Math.Ceiling(count / maxCount));
            var stepLength = convertParam.Length * step / count;
            var smallMaxCount = Convert.ToInt32(Math.Ceiling(stepLength / 20));
            var smallStep = Convert.ToInt32(Math.Ceiling(step * 60d / smallMaxCount));
            if (smallStep >= 60) smallStep = Convert.ToInt32(Math.Ceiling(smallStep / 60d) * 60);
            else if (smallStep >= 30) smallStep = 30;
            else if (smallStep >= 20) smallStep = 20;
            else if (smallStep >= 10) smallStep = 10;
            else if (smallStep >= 5) smallStep = 5;
            else smallStep = 0;
            var startDate = new DateTime(begin.Year, begin.Month, begin.Day, begin.Hour, begin.Minute, 0);
            for (int y = 0; y <= count; y += step)
            {
                var date = startDate.AddMinutes(y);
                var location = GetValueLocation(date.Ticks / ThicksToSeconds, convertParam);
                DrawSeparate(dc, location, date.Minute.ToString(), FontSize, 5, convertParam);
                if (smallStep <= 0) continue;
                for (int m = 1; m <= step * 60 / smallStep; m++) DrawSmallSeparate(dc, GetValueLocation(date.AddSeconds(m * smallStep).Ticks / ThicksToSeconds, convertParam), convertParam);
            }
        }
        /// <summary>
        /// 跨秒绘制
        /// </summary>
        /// <param name="dc"></param>
        /// <param name="begin"></param>
        /// <param name="end"></param>
        /// <param name="convertParam"></param>
        private void DrawSecond(DrawingContext dc, DateTime begin, DateTime end, ValueLocationConvertParam convertParam)
        {
            CreateTips(dc, begin.ToString("yyyy-MM-dd HH:mm"), end.ToString("yyyy-MM-dd HH:mm"), convertParam);
            int maxCount = Convert.ToInt32(convertParam.Length / (2 * FontSize));
            if (maxCount <= 0) return;
            var count = (end - begin).TotalSeconds + 1;
            var step = Convert.ToInt32(Math.Ceiling(count / maxCount));
            var startDate = new DateTime(begin.Year, begin.Month, begin.Day, begin.Hour, begin.Minute, begin.Second);
            for (int y = 0; y <= count; y += step)
            {
                var date = startDate.AddSeconds(y);
                var location = GetValueLocation(date.Ticks / ThicksToSeconds, convertParam);
                DrawSeparate(dc, location, date.Second.ToString(), FontSize, 5, convertParam);
            }
        }

        /// <summary>
        /// 绘制右下角标
        /// </summary>
        /// <param name="dc"></param>
        /// <param name="startStr"></param>
        /// <param name="stopStr"></param>
        /// <param name="convertParam"></param>
        private void CreateTips(DrawingContext dc, string startStr, string stopStr, ValueLocationConvertParam convertParam)
        {
            var start = CreateTextGeometry(startStr, FontSize);
            var stop = CreateTextGeometry(stopStr, FontSize);
            if (convertParam.AxisType == AxisType.X)
            {
                start.Item1.Transform = new MatrixTransform(1, 0, 0, 1, 0, this.ActualHeight - start.Item3);
                stop.Item1.Transform = new MatrixTransform(1, 0, 0, 1, this.ActualWidth - stop.Item2, this.ActualHeight - stop.Item3);
            }
            else if (convertParam.AxisType == AxisType.Y)
            {
                start.Item1.Transform = new MatrixTransform(0, -1, 1, 0, 0, this.ActualHeight);
                stop.Item1.Transform = new MatrixTransform(0, -1, 1, 0, 0, stop.Item2);
            }
            else
            {
                start.Item1.Transform = new MatrixTransform(0, 1, -1, 0, this.ActualWidth, this.ActualHeight - start.Item2);
                stop.Item1.Transform = new MatrixTransform(0, 1, -1, 0, this.ActualWidth, 0);
            }
            dc.DrawGeometry(Foreground, StringPen, start.Item1);
            dc.DrawGeometry(Foreground, StringPen, stop.Item1);
        }
    }
}
